package storage

import (
	"bytes"
	"fmt"
	"hash"
	"sync"

	"github.com/caivega/glog"
	"github.com/tokentransfer/node/core"
	"github.com/tokentransfer/node/util"
)

type listItem struct {
	item    *chunkItem
	changed bool
}

type chunkList struct {
	storage *chunkStorage
	root    *chunkGroup
	name    string
	hash    hash.Hash
	parent  *chunkList

	locker    *sync.Mutex
	itemArray []*listItem
	itemMap   map[string]int

	lastKey   *core.Key
	itemKey   *core.Key
	itemEntry *chunkEntry
}

func NewList(s *chunkStorage, r *chunkGroup, name string) (core.List, error) {
	g := &chunkList{
		storage: s,
		root:    r,
		name:    name,
		hash:    cs.NewHasher(),
		parent:  nil,

		locker:    &sync.Mutex{},
		itemArray: make([]*listItem, 0),
		itemMap:   make(map[string]int),
	}
	err := g.load(name)
	if err != nil {
		return nil, err
	}
	return g, nil
}

func (f *chunkList) Root() core.Group {
	return f.root
}

func (f *chunkList) Parent() core.List {
	return f.parent
}

func (f *chunkList) List(name string) (core.List, error) {
	f.locker.Lock()
	defer f.locker.Unlock()

	if !f.Exists(name) {
		return nil, util.ErrorOfNotFound("list", name)
	}

	item, err := f.get(name)
	if err != nil {
		return nil, err
	}
	if !item.group {
		return nil, util.ErrorOfInvalid("list", name)
	}
	g := &chunkList{
		storage: f.storage,
		root:    f.root,
		name:    name,
		hash:    cs.NewHasher(),
		parent:  f,

		locker:    &sync.Mutex{},
		itemArray: make([]*listItem, 0),
		itemMap:   make(map[string]int),
	}
	err = g.Load(item.key)
	if err != nil {
		return nil, err
	}
	return g, nil
}

func (f *chunkList) AddList(name string) (core.List, error) {
	f.locker.Lock()
	defer f.locker.Unlock()

	if f.Exists(name) {
		return nil, util.ErrorOf("Already exists", "list", name)
	}
	g := &chunkList{
		storage: f.storage,
		root:    f.root,
		name:    name,
		hash:    cs.NewHasher(),
		parent:  f,

		locker:    &sync.Mutex{},
		itemArray: make([]*listItem, 0),
		itemMap:   make(map[string]int),
	}
	err := g.update()
	if err != nil {
		return nil, err
	}
	item := &chunkItem{
		name:  name,
		key:   *g.itemKey,
		group: true,
	}
	f.itemMap[name] = len(f.itemArray)
	// f.itemMap[item.key.String()] = len(f.itemArray)
	f.itemArray = append(f.itemArray, &listItem{item, true})
	err = f.update()
	if err != nil {
		return nil, err
	}

	return g, nil
}

func (f *chunkList) Dump(log core.Printer) {
	log.Printf("name: %s, size: %d, parent: %s", f.name, len(f.itemMap), func(d *chunkList) string {
		if d != nil {
			return d.name
		}
		return "nil"
	}(f.parent))

	for i := 0; i < len(f.itemArray); i++ {
		item := f.itemArray[i]
		if item != nil {
			log.Printf("%d: %s, %s, %v, %v", i, item.item.name, item.item.key.String(), item.item.group, item.changed)
		}
	}
}

func (f *chunkList) dispose(name string, key core.Key) {
	f.locker.Lock()
	defer f.locker.Unlock()

	err := f.storage.releaseL(key)
	if err != nil {
		glog.Error(err)
	}
}

func (f *chunkList) Dispose() {
	if f.parent != nil {
		if f.itemKey != nil {
			f.dispose(f.name, *f.itemKey)
		}
	}
}

func (f *chunkList) Load(key core.Key) error {
	entry, ok := f.storage.getEntry(key)
	if ok {
		f.name = entry.name
		f.lastKey = &key
		f.itemKey = &key
		f.itemEntry = entry

		for i := 0; i < len(entry.chunks); i++ {
			chunk := entry.chunks[i]
			item := &chunkItem{}
			if i == 0 {
				err := f.storage.cb.unmarshalItem(item, entry.data[:chunk.nextPos])
				if err != nil {
					return err
				}
			} else {
				err := f.storage.cb.unmarshalItem(item, entry.data[entry.chunks[i-1].nextPos:chunk.nextPos])
				if err != nil {
					return err
				}
			}
			f.itemMap[item.name] = len(f.itemArray)
			// f.itemMap[item.key.String()] = len(f.itemArray)

			f.itemArray = append(f.itemArray, &listItem{item, false})
		}
		return nil
	} else {
		return util.ErrorOfNotFound("key", key.String())
	}
}

func (f *chunkList) get(name string) (*chunkItem, error) {
	index, ok := f.itemMap[name]
	if !ok {
		return nil, util.ErrorOfNotFound("group", name)
	}
	item := f.itemArray[index]
	if item == nil {
		return nil, util.ErrorOfInvalid("state", fmt.Sprintf("%d", index))
	}
	return item.item, nil
}

func (f *chunkList) exists(name string) (core.Key, bool, error) {
	link := core.GetKey(name)
	_, ok := f.storage.getEntry(link)
	if ok {
		return link, ok, nil
	} else {
		return nil, false, nil
	}
}

func (f *chunkList) load(name string) error {
	link, ok, err := f.exists(name)
	if err != nil {
		return err
	}
	if ok {
		linkEntry, ok := f.storage.getEntry(link)
		if ok {
			key, err := core.ParseKey(linkEntry.name)
			if err != nil {
				return err
			}
			err = f.Load(key)
			if err != nil {
				return err
			}
		} else {
			return util.ErrorOfNotFound("group", name)
		}
	}

	err = f.update()
	if err != nil {
		return err
	}

	return nil
}

func (f *chunkList) update() error {
	lm := len(f.itemMap)
	la := len(f.itemArray)
	if la > 32 && (la+1) > 2*(lm+1) {
		itemMap := make(map[string]int)
		itemArray := make([]*listItem, lm)
		c := 0
		for i := 0; i < la; i++ {
			item := f.itemArray[i]
			if item != nil {
				itemArray[c] = item
				itemMap[item.item.name] = c
				c++
			}
		}
		f.itemMap = itemMap
		f.itemArray = itemArray
	}

	buf := new(bytes.Buffer)
	chunks := make([]chunkOffset, 0)
	for i := 0; i < len(f.itemArray); i++ {
		item := f.itemArray[i]
		if item != nil {
			data, err := f.storage.cb.marshalItem(item.item)
			if err != nil {
				return err
			}
			_, err = buf.Write(data)
			if err != nil {
				return err
			}
			chunks = append(chunks, chunkOffset{
				key:     item.item.key,
				nextPos: int64(buf.Len()),
			})
		}
	}
	entry := &chunkEntry{
		name:   f.name,
		data:   buf.Bytes(),
		chunks: chunks,
		refs:   1,
	}
	data, err := f.storage.cb.marshalEntry(entry)
	if err != nil {
		return err
	}
	_, err = f.hash.Write(data)
	if err != nil {
		return err
	}
	key := core.Key(f.hash.Sum(nil))
	f.hash.Reset()

	f.itemKey = &key
	f.itemEntry = entry

	return nil
}

func (f *chunkList) Name() string {
	return f.name
}

func (f *chunkList) Key() core.Key {
	if f.itemKey != nil {
		return *f.itemKey
	}
	return core.Key{}
}

func (f *chunkList) AddData(name string, key core.Key) (bool, error) {
	f.locker.Lock()
	defer f.locker.Unlock()

	var item *listItem = nil

	index, ok := f.itemMap[name]
	if ok {
		item = f.itemArray[index]
		if item != nil {
			if item.item.key.String() == key.String() {
				err := f.storage.lockL(key)
				if err != nil {
					return false, err
				}
				return true, nil
			} else {
				if item.item.group {
					return false, util.ErrorOfInvalid("replace a list", "data")
				} else {
					err := f.storage.releaseL(item.item.key)
					if err != nil {
						return false, err
					}
				}
				item.item.key = key
			}
		}
	}
	err := f.storage.lockL(key)
	if err != nil {
		return false, err
	}
	if item == nil {
		item = &listItem{
			item: &chunkItem{
				name:  name,
				key:   key,
				group: false,
			},
			changed: true,
		}
		f.itemMap[name] = len(f.itemArray)
		// f.itemMap[item.key.String()] = len(f.itemArray)
		f.itemArray = append(f.itemArray, item)
	}
	err = f.update()
	if err != nil {
		return false, err
	}

	return false, nil
}

func (f *chunkList) Remove(name string) error {
	f.locker.Lock()
	defer f.locker.Unlock()

	index, ok := f.itemMap[name]
	if ok {
		item := f.itemArray[index]
		if item != nil {
			err := f.storage.releaseL(item.item.key)
			if err != nil {
				return err
			}
		}

		delete(f.itemMap, name)
		f.itemArray[index] = nil

		err := f.update()
		if err != nil {
			return err
		}
	}

	return nil
}

func (f *chunkList) Exists(name string) bool {
	_, ok := f.itemMap[name]
	return ok
}

func (f *chunkList) GetKey(name string) (core.Key, error) {
	f.locker.Lock()
	defer f.locker.Unlock()

	index, ok := f.itemMap[name]
	if ok {
		item := f.itemArray[index]
		if item != nil {
			return item.item.key, nil
		} else {
			return nil, util.ErrorOfInvalid("state", fmt.Sprintf("%d", index))
		}
	}
	return nil, util.ErrorOfNotFound("key", name)
}

func (f *chunkList) IsList(name string) (bool, error) {
	f.locker.Lock()
	defer f.locker.Unlock()

	index, ok := f.itemMap[name]
	if ok {
		item := f.itemArray[index]
		if item != nil {
			return item.item.group, nil
		} else {
			return false, util.ErrorOfInvalid("state", fmt.Sprintf("%d", index))
		}
	}
	return false, util.ErrorOfNotFound("group", name)
}

func (f *chunkList) Items() []string {
	f.locker.Lock()
	defer f.locker.Unlock()

	list := make([]string, 0)
	for i := 0; i < len(f.itemArray); i++ {
		item := f.itemArray[i]
		if item != nil {
			list = append(list, item.item.name)
		}
	}
	return list
}

func (f *chunkList) Commit() error {
	f.locker.Lock()
	defer f.locker.Unlock()

	if f.itemKey != nil && f.itemEntry != nil && f.itemKey != f.lastKey {
		if _, _, err := f.storage.putEntry(*f.itemKey, f.itemEntry); err != nil {
			return err
		}
		if f.lastKey != nil {
			err := f.storage.releaseL(*f.lastKey)
			if err != nil {
				return err
			}
		}
		if f.parent != nil {
			item, err := f.parent.get(f.name)
			if err != nil {
				return err
			}
			item.key = *f.itemKey
			err = f.parent.update()
			if err != nil {
				return err
			}
			err = f.parent.Commit()
			if err != nil {
				return err
			}
		} else {
			if f.root != nil {
				item, err := f.root.get(f.name)
				if err != nil {
					return err
				}
				item.key = *f.itemKey
				err = f.root.update()
				if err != nil {
					return err
				}
			} else {
				err := f.storage.addList(f.name, *f.itemKey)
				if err != nil {
					return err
				}
				err = f.storage.flush()
				if err != nil {
					return err
				}
				err = f.storage.entries.Flush()
				if err != nil {
					return err
				}
			}
		}
		f.lastKey = f.itemKey
	}
	return nil
}
